package service

import (
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"gitee.com/zh-jn/harmonica-previewer-online/api"
	"gitee.com/zh-jn/harmonica-previewer-online/compiler/deveco-compiler/v1/config"
)

type workloadHandler struct {
	codeBundle []byte
	errCh      chan error
	jsBundle   chan []byte
}

func NewCompileMaster(agentNum int, compileEnvZipPath, compileAgentRunningHomeBase string, compileTimeout, workloadsQueueSize int, compileCommand string) *CompileMaster {
	return &CompileMaster{
		agentNum:                    agentNum,
		compileEnvZipPath:           compileEnvZipPath,
		compileAgentRunningHomeBase: compileAgentRunningHomeBase,
		compileCommand:              compileCommand,
		compileTimeout:              compileTimeout,
		workloads:                   make(chan *workloadHandler, workloadsQueueSize),
	}
}

type CompileMaster struct {
	agentNum                    int
	workloads                   chan *workloadHandler
	compileEnvZipPath           string
	compileAgentRunningHomeBase string
	compileCommand              string

	compileTimeout int // second
}

func (cm *CompileMaster) Start() error {
	cmd := strings.Split(cm.compileCommand, " ")
	errch := make(chan error, 1)
	for i := 0; i < cm.agentNum; i++ {
		go func(id int) {
			agent := NewCompileAgent(fmt.Sprintf("compile_agent_%v", id), cm.compileEnvZipPath, cm.compileAgentRunningHomeBase, cmd)
			if err := agent.Init(); err != nil {
				errch <- fmt.Errorf("agent %v init fail, reason: %v", id, err)
				return
			}
			for workload := range cm.workloads {
				jsbundle, output, err := agent.Compile(workload.codeBundle)
				fmt.Printf("compile agent [%v] output: %v, err: %v\n", id, output, err)
				if err != nil {
					workload.errCh <- fmt.Errorf("compile err, %v\n output = %v", err, output)
					continue
				}

				workload.jsBundle <- jsbundle
			}
		}(i)
	}
	return <-errch
}

func (cm *CompileMaster) Compile(codeBundle []byte) ([]byte, error) {
	errCh := make(chan error, 1)
	jsBundle := make(chan []byte, 1)
	timer := time.NewTimer(time.Duration(cm.compileTimeout) * time.Second)

	workload := &workloadHandler{
		codeBundle: codeBundle,
		errCh:      errCh,
		jsBundle:   jsBundle,
	}

	select {
	case cm.workloads <- workload:
	case <-timer.C:
		timer.Stop()
		close(errCh)
		close(jsBundle)
		return nil, fmt.Errorf("set workloads timeout after %v seconds", cm.compileTimeout)
	}

	for {
		select {
		case jsbundle := <-jsBundle:
			return jsbundle, nil
		case err := <-errCh:
			return nil, err
		case <-timer.C:
			timer.Stop()
			close(errCh)
			close(jsBundle)
			return nil, fmt.Errorf("compile timeout after %v seconds", cm.compileTimeout)
		}
	}
}

func RunServer(cfg config.Config) {
	c := NewCompileMaster(cfg.AgentNum, cfg.CompileEnvZip, cfg.RunningHomeBase, cfg.CompileTimeout, cfg.WorkloadsQueueSize, cfg.CompileCommand)
	go func() {
		for {
			if err := c.Start(); err != nil {
				panic(err)
			}
		}
	}()
	time.Sleep(1 * time.Second)

	if err := http.ListenAndServe(fmt.Sprintf("%v:%v", cfg.Addr, cfg.Port), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		codeBundle, err := ioutil.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(http.StatusOK)
			w.Write(api.RspBuf(nil, err))
			return
		}
		jsBundle, err := c.Compile(codeBundle)
		if err != nil {
			w.WriteHeader(http.StatusOK)
			w.Write(api.RspBuf(nil, err))
			return
		}
		jsBundleB64 := base64.StdEncoding.EncodeToString(jsBundle)
		w.WriteHeader(http.StatusOK)
		w.Write(api.RspBuf(jsBundleB64, nil))
		return
	})); err != nil {
		panic(err)
	}
}
